package com.chenchao.javafile;


import java.io.*;
import java.util.*;

/**
 * @encoding : UTF-8
 * @Time : 2020/11/19 19:33
 * @Author : Chen Chao
 * @File : javaSE.com.chenchao.javafile.Test17.java
 * @Software : IntelliJ IDEA
 * @since : 1.8
 * 随机文件流
 */
public class Test17 {
    public static void main(String[] args) {
        //test01();
        //test02();
        //test03();
        test04();

    }

    /**
     * 创建DoFile对象测试文件拆分与合并
     */
    public static void test01() {
        DoFile doFile = new DoFile("myjava01/src/img/gg.jpg",
                "myjava01/src/fo/", 10, 1);
        System.out.println(doFile.toString());
        new Thread(doFile).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        doFile.mergeP();

    }

    /**
     * 测试追加方式合并文件
     */
    public static void test02() {
        DoFile.merge(new File("myjava01/src/fo/test"), new File("myjava01/src/fo/test001.mp4"));
    }

    /**
     * 测试多线程，（目前不知道是否可以多线程写入）
     */
    public static void test03() {
        DoFile doFile = new DoFile("myjava01/src/img/test.mp4",
                "myjava01/src/fo/", 10, 2);
        new Thread(doFile).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        doFile.download(3);
        System.out.println(doFile.toString());
    }

    /**
     * 测试序列流拼合
     */
    public static void test04(){
        DoFile doFile = new DoFile("myjava01/src/img/妖怪橘子 - 落花情.mp3",
                "myjava01/src/fo/", 1, 2);
        System.out.println(doFile.toString());
        new Thread(doFile).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        doFile.mergeS();
    }
}

class DoFile implements Runnable {
    private final static int MAX_BLOCK_NUM = 100;
    private final int unitSize = 1024;
    private File filePath;
    private File dirPath;
    private int blockSize;
    private int blockNum;
    private final HashMap<Integer, File> integerFileHashMap = new HashMap<>();


    /**
     * @param filePath  要分割的文件路径
     * @param dirPath   分割后的文件路径
     * @param blockSize 每一块文件大小
     * @param unit      为每一块单位，0取B 1取KB 2取MB
     *                  this.blockSize*this.unitSize才是每一块真实的大小
     */
    public DoFile(File filePath, File dirPath, int blockSize, int unit) {
        this.filePath = filePath;
        this.dirPath = dirPath;
        this.blockSize = (int) (blockSize * Math.pow(unitSize, unit));
        this.blockNum = (int) Math.ceil(filePath.length() / 1.0 / this.blockSize);
    }

    /**
     * @param filePath  要分割的文件路径
     * @param dirPath   分割后的文件路径
     * @param blockSize 分割后的文件路径
     * @param unit      为每一块单位，0取B 1取KB 2取MB
     */
    public DoFile(String filePath, String dirPath, int blockSize, int unit) {
        this(new File(filePath), new File(dirPath), blockSize, unit);
    }

    /**
     * @param filePath  要分割的文件路径
     * @param dirPath   分割后的文件路径
     * @param blockSize 分割后的文件路径
     * @param unit      为每一块单位，0取B 1取KB 2取MB
     */
    public DoFile(String filePath, File dirPath, int blockSize, int unit) {
        this(new File(filePath), dirPath, blockSize, unit);
    }

    /**
     * @param filePath  要分割的文件路径
     * @param dirPath   分割后的文件路径
     * @param blockSize 分割后的文件路径
     * @param unit      为每一块单位，0取B 1取KB 2取MB
     */
    public DoFile(File filePath, String dirPath, int blockSize, int unit) {
        this(filePath, new File(dirPath), blockSize, unit);

    }

    /**
     * 多线程下载
     *
     * @param i 几个线程
     */
    public void download(int i) {
        for (int j = 0; j < i; j++) {
            this.mergeP();
        }
    }

    /**
     * 下载文件
     */
    @Override
    public void run() {
        int i = 0;
        int j;
        if (this.blockNum > MAX_BLOCK_NUM) {
            System.out.println("分片数量太大");
            return;
        }
        while (i < this.blockNum) {
            synchronized (this) {
                j = i++;
            }
            this.writeToFile(j);
        }
    }

    /**
     * 对指定文件夹下的文件名进序列化
     *
     * @param file 要序列化的文件夹
     */
    public static void listFileRename(File file) {
        if (file.isDirectory()) {
            for (File listFile : Objects.requireNonNull(file.listFiles())) {
                File numFile = new File(file, String.format("g%03d", Integer.parseInt(listFile.getName())));
                boolean b = listFile.renameTo(numFile);
                if (!b) {
                    System.out.println("重命名失败！");
                } else {
                    System.out.println("重命名成功！");
                }
            }
        }
    }


    /**
     * 把内容分割写入分文件
     *
     * @param blockNumber 写入分文件的编号
     */
    public void writeToFile(int blockNumber) {
        //创建输入输出流对象
        try (RandomAccessFile r = new RandomAccessFile(this.getFilePath(), "r");
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream
                     (new FileOutputStream(Objects.requireNonNull(this.createNumFile(blockNumber))))) {
            byte[] bytes = new byte[1024];
            //跳到指定位置
            r.seek(blockNumber * this.blockSize);

            //拷贝文件
            int countSize = this.blockSize;
            //计算该块的剩余容量
            int len;
            while ((len = r.read(bytes)) != -1) {
                //对改块中的数据进行判断还有没有
                if (countSize > 0) {
                    bufferedOutputStream.write(bytes, 0, len);
                    countSize -= len;
                } else {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    /**
     * 创建分文件
     *
     * @param blockNumber 要创建的分文件数量
     * @return 创建的分文件抽象地址
     */
    public File createNumFile(int blockNumber) {
        if (blockNum <= blockNumber || blockNumber < 0) {
            return null;
        }
        //创建分文件
        StringBuilder str = new StringBuilder(this.filePath.getName());
        str.insert(this.filePath.getName().lastIndexOf("."),
                "_" + String.format("%02d", blockNumber + 1));
        //给分文件名插入序号
        File file = new File(this.dirPath.getPath(), String.valueOf(str));
        this.integerFileHashMap.put(blockNumber, file);
        return file;
    }
    /**
     * 序列化拼合
     */
    public  void mergeS(){
        merge(this.integerFileHashMap,new File(this.dirPath, this.filePath.getName()));
    }
    /**
     * 序列化拼合
     * @param integerFileHashMap 分文件列表
     * @param totalFile  拼合文件
     * @throws IOException Io异常
     */
    public void mergeS(HashMap<Integer, File> integerFileHashMap, File totalFile) throws IOException {
        Vector<BufferedInputStream> bufferedInputStreams = new Vector<>();
        Set<Map.Entry<Integer, File>> entries = integerFileHashMap.entrySet();
        for (Map.Entry<Integer, File> entry : entries) {
            bufferedInputStreams.add(new BufferedInputStream
                    (new FileInputStream(entry.getValue())));
        }
        //创建序列流
        try (SequenceInputStream sequenceInputStream = new SequenceInputStream(bufferedInputStreams.elements());
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream
                     (new FileOutputStream(totalFile, true))
        ) {
            //拷贝文件
            byte[] bytes = new byte[1024];
            int len;
            //分文件不用担心溢出问题
            while ((len = sequenceInputStream.read(bytes)) != -1) {
                bufferedOutputStream.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * BufferedOutputStream 追加方式合并文件
     *
     * @param integerFileHashMap 要合并的文件Map,key是Integer,value是File
     * @param totalFile          合并的文件位置
     */
    public static void merge(HashMap<Integer, File> integerFileHashMap, File totalFile) {
        Set<Map.Entry<Integer, File>> entries = integerFileHashMap.entrySet();
        //遍历列表中的每一个文件
        for (Map.Entry<Integer, File> entry : entries) {
            System.out.println(entry.getKey() + "->" + entry.getValue());
            try (BufferedInputStream bufferedInputStream = new BufferedInputStream
                    (new FileInputStream(entry.getValue()));
                 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream
                         (new FileOutputStream(totalFile, true))
                 //注意要追加写不要覆盖写
            ) {

                //拷贝文件
                byte[] bytes = new byte[1024];
                int len;
                //分文件不用担心溢出问题
                while ((len = bufferedInputStream.read(bytes)) != -1) {
                    bufferedOutputStream.write(bytes, 0, len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * RandomAccessFile 方法合并文件
     *
     * @param integerFileHashMap 要合并的文件Map,key是Integer,value是File
     * @param totalFile          合并的文件位置
     * @param blockSize          RandomAccessFile需要每个文件大小相同，这里就是分文件大小
     */
    public static void mergeP(HashMap<Integer, File> integerFileHashMap, File totalFile, int blockSize) {
        Set<Map.Entry<Integer, File>> entries = integerFileHashMap.entrySet();
        //遍历列表中的每一个文件
        for (Map.Entry<Integer, File> entry : entries) {
            try (BufferedInputStream bufferedInputStream = new BufferedInputStream
                    (new FileInputStream(entry.getValue()));
                 RandomAccessFile randomAccessFile = new RandomAccessFile(totalFile, "rw")) {
                byte[] bytes = new byte[1024];
                int len;
                randomAccessFile.seek(entry.getKey() * blockSize);
                //分文件不用担心溢出问题
                while ((len = bufferedInputStream.read(bytes)) != -1) {
                    randomAccessFile.write(bytes, 0, len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            //删除分文件
            if (entry.getValue().delete()) {
                System.out.println("删除成功");
            }
        }
    }

    /**
     * 把指定文件夹下的文件合并
     *
     * @param listFilePath 要合并的文件夹
     * @param totalFile    合并的文件位置
     */
    public static void merge(File listFilePath, File totalFile) {
        //listFileRename(listFilePath);
        if (listFilePath.isDirectory()) {
            int listLength = Objects.requireNonNull(listFilePath.listFiles()).length;
            //获得列表文件数量

            if (listLength == 0) {
                System.out.println("无要合并的文件");
            }
            int i = 0;
            HashMap<Integer, File> integerFileHashMap = new HashMap<>(listLength);
            for (File file : Objects.requireNonNull(listFilePath.listFiles())) {
                if (file.isFile()) {
                    integerFileHashMap.put(i++, file);
                }
            }

            merge(integerFileHashMap, totalFile);
        } else {
            System.out.println("文件路径不存在或者不为文件夹");
        }
    }

    /**
     * 默认合并方式
     */
    public void mergeP() {
        mergeP(new File(this.dirPath, this.filePath.getName()));
    }

    /**
     * 合并到指定文件位置
     *
     * @param file 指定文件位置
     */
    public void mergeP(File file) {
        mergeP(this.integerFileHashMap, file, this.blockSize);
    }

    public File getFilePath() {
        return filePath;
    }

    public void setFilePath(File filePath) {
        this.filePath = filePath;
    }

    public File getDirPath() {
        return dirPath;
    }

    public void setDirPath(File dirPath) {
        this.dirPath = dirPath;
    }

    public int getBlockSize() {
        return blockSize;
    }

    public void setBlockSize(int blockSize) {
        this.blockSize = blockSize;
    }

    public int getBlockNum() {
        return blockNum;
    }

    public void setBlockNum(int blockNum) {
        this.blockNum = blockNum;
    }

    @Override
    public String toString() {
        return "DoFile{" +
                "unitSize=" + unitSize +
                ", filePath=" + filePath +
                ", dirPath=" + dirPath +
                ", blockSize=" + (blockSize >>> 10) + "KB" +
                ", blockNum=" + blockNum +
                '}';
    }

}